<?php


namespace EchoPHP\Database\Connections;

use Closure;
use EchoPHP\Database\Query\Grammar\MysqlGrammar;
use EchoPHP\Database\Query\Grammar\OracleGrammar;
use EchoPHP\Database\Query\Grammar\SqlServerGrammar;
use EchoPHP\Database\TuxedoDriver\TuxedoStatement;
use EchoPHP\Support\Arr;
use InvalidArgumentException;
use PDO;
use PDOStatement;

class PdoConnection extends Connection {

    /**
     * 获取默认语法解析器
     * @return MysqlGrammar|OracleGrammar|SqlServerGrammar
     */
    protected function getDefaultQueryGrammar() {
        $driver = $this->config['driver'] ?? '';
        switch (strtoupper($driver)) {
            case 'ORACLE':
                return new OracleGrammar();
            case 'MYSQL':
                return new MysqlGrammar();
            case 'SQLSERVER':
                return new SqlServerGrammar();
            default:
                throw new InvalidArgumentException('数据库类型定义不正确');
        }
    }

    /**
     * 执行查询操作
     * @param string $query
     * @param array $bindings
     * @param int $rowNumFrom
     * @param int $rowNumTo
     * @return array|mixed
     * @throws \Exception
     */
    public function select(string $query, array $bindings = [], int $rowNumFrom = 0, int $rowNumTo = 0) {
        $query = $this->compileSelectLimit($query, $bindings, $rowNumFrom, $rowNumTo);
        return $this->run($query, $bindings, function ($query, $bindings) {
            $statement = $this->getConnector()->prepare($query);
            $statement->setFetchMode($this->fetchMode);

            $this->bindValuesToPDOStatement($statement, $this->prepareBindings($bindings));

            $statement->execute();

            return $statement->fetchAll();
        });
    }

    /**
     * 执行sql查询并返回一个迭代器
     * @param string $query
     * @param array $bindings
     * @param int $rowNumFrom
     * @param int $rowNumTo
     * @return \Generator
     * @throws \Exception
     */
    public function selectCursor(string $query, array $bindings = [], int $rowNumFrom = 0, int $rowNumTo = 0) {
        $query = $this->compileSelectLimit($query, $bindings, $rowNumFrom, $rowNumTo);
        $statement = $this->run($query, $bindings, function ($query, $bindings) {
            $statement = $this->getConnector()->prepare($query);
            $statement->setFetchMode($this->fetchMode);

            $this->bindValuesToPDOStatement($statement, $this->prepareBindings($bindings));

            $statement->execute();
            return $statement;
        });

        while ($record = $statement->fetch()) {
            yield $record;
        }
    }

    /**
     * 执行sql查询并返回第一条数据
     * @param string $query
     * @param array $bindings
     * @return mixed
     * @throws \Exception
     */
    public function selectOne(string $query, array $bindings = []) {
        $records = $this->select($query, $bindings);

        return array_shift($records);
    }

    /**
     * 执行插入语句
     * @param string $query
     * @param array $bindings
     * @return bool
     */
    public function insert(string $query, array $bindings = []) {
        return $this->execute($query, $bindings);
    }

    /**
     * 执行更新语句
     * @param string $query
     * @param array $bindings
     * @return int
     */
    public function update(string $query, array $bindings = []) {
        return $this->executeWithAffectedRows($query, $bindings);
    }

    /**
     * 执行删除语句
     * @param string $query
     * @param array $bindings
     * @return int
     */
    public function delete(string $query, array $bindings = []) {
        return $this->executeWithAffectedRows($query, $bindings);
    }

    /**
     * 返回PDOStatement，语句操作类
     * @param string $query
     * @param array $bindings
     * @return bool|\EchoPHP\Database\TuxedoDriver\TuxedoStatement|PDOStatement
     * @throws \Exception
     */
    public function prepareStatement(string $query, array $bindings = []) {
        $statement = $this->getConnector()->prepare($query);
        $this->bindValuesToPDOStatement($statement, $this->prepareBindings($bindings));

        return $statement;
    }

    /**
     * tuxedo单库事务操作，PDO操作不支持
     * @param bool $rollbackIfNoAffects
     * @param TuxedoStatement ...$tuxedoStatements
     */
    public function prepareTransactionStatement(bool $rollbackIfNoAffects, TuxedoStatement ...$tuxedoStatements) {
        throw new InvalidArgumentException('PDO操作不支持该方法');
    }

    /**
     * 直接执行操作类的sql语句，返回执行结果
     * @param string $query
     * @param array $bindings
     * @return bool
     */
    public function execute(string $query, array $bindings = []) {
        return $this->run($query, $bindings, function ($query, $bindings) {
            $statement = $this->getConnector()->prepare($query);

            $this->bindValuesToPDOStatement($statement, $this->prepareBindings($bindings));

            return $statement->execute();
        });
    }

    /**
     * 直接执行操作类的sql语句，返回执行受影响行数
     * @param string $query
     * @param array $bindings
     * @return mixed
     */
    public function executeWithAffectedRows(string $query, array $bindings = []) {
        return $this->run($query, $bindings, function ($query, $bindings) {
            $statement = $this->getConnector()->prepare($query);

            $this->bindValuesToPDOStatement($statement, $this->prepareBindings($bindings));

            $statement->execute();

            return $statement->rowCount();
        });
    }

    /**
     * 获取最后插入id
     * @param null $sequence
     * @return string
     */
    public function getLastInsertId($sequence = null) {
        if (!$this->connector) {
            return 0;
        }
        return $this->getConnector()->lastInsertId($sequence);
    }

    /**
     * PDOStatement 绑定变量
     * @param PDOStatement $statement
     * @param array $bindings
     */
    protected function bindValuesToPDOStatement(PDOStatement $statement, array $bindings) {
        foreach ($bindings as $key => $value) {
            $statement->bindValue(
                is_string($key) ? $key : $key + 1,
                $value,
                is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR
            );
        }
    }

    /**
     * sql执行方法
     * @param $query
     * @param $bindings
     * @param Closure $callback
     * @return mixed
     * @throws \Exception
     */
    protected function run($query, $bindings, Closure $callback) {
        Arr::convertCharset($bindings, 'GBK', 'UTF8');
        $result = parent::run($query, $bindings, $callback);
        Arr::convertCharset($result, 'UTF8', 'GBK');
        return $result;
    }
}